#include <assert.h>
#include <cassert>
#include <iomanip>
#include <ostream>
#include <stdexcept>
#include <stdlib.h>

#include "../headers/FieldSpecifications.h"
#include "../headers/BaseWord.h"

using namespace std;

BaseWord::BaseWord(const unsigned short size)
{
	this->initialize(size);
}


BaseWord::BaseWord(const unsigned short size, const signed char sign, const unsigned char values[])
{
	this->initialize(size);
	this->value(sign, values);
}


BaseWord::~BaseWord(void)
{
	delete[] this->pValues;
}


void BaseWord::initialize(const unsigned short size)
{
	this->size = size;
	this->set_sign(+1);
	this->pValues = new unsigned char[size];
	for(int n = 0; n < size; n++)
	{
		this->pValues[n] = 0;
	}
}


void BaseWord::set_sign(const signed char sign)
{
	this->sign = (sign > 0 ? +1 : (sign < 0 ? -1 : 0));
}


unsigned short BaseWord::get_size() const
{
	return this->size;
}


signed int BaseWord::value() const
{
	signed int result = 0;
	for(int n = 0; n < this->get_size(); n++)
	{
		result *= BaseWord::DISTINCT_BYTE_VALUES;
		result += (signed int)this->pValues[n];
	}

	return result * (signed int)this->sign;
}


void BaseWord::value(const signed char sign, const unsigned char values[])
{
	this->set_sign(sign);
	this->value(values);
}


void BaseWord::value(const unsigned char values[])
{
	for(int n = 0; n < this->size; n++)
	{
		this->pValues[n] = values[n] % BaseWord::DISTINCT_BYTE_VALUES;
	}
}


void BaseWord::write_word(std::ostream* const os) const
{
	this->write_word(os, true);
}


void BaseWord::write_word(std::ostream* const os, const bool revertHex) const
{
	*(os) << (this->sign > 0 ? '+' : (this->sign < 0 ? '-' : '~')) << "0x";
	for(int n = 0; n < this->size; n++)
	{
		*(os) << std::hex << std::setfill('0') << std::setw(2) << (int)this->pValues[n];
	}

	if(revertHex)
	{
		*(os) << std::dec;
	}
}


void BaseWord::deserialize(std::istream* const fs)
{
	// sign
	fs->read((char*)&(this->sign), sizeof(signed char));

	// bytes
	fs->read((char*)this->pValues, this->size * sizeof(unsigned char));

	// normalize bytes
	for(int n = 0; n < this->size; n++)
	{
		this->pValues[n] %= BaseWord::DISTINCT_BYTE_VALUES;
	}
}


void BaseWord::serialize(std::ostream* const fs) const
{
	// sign
	fs->write((char*)&(this->sign), sizeof(signed char));

	// bytes
	fs->write((char*)this->pValues, this->size * sizeof(unsigned char));
}


unsigned char* const BaseWord::value_bytes() const
{
	return this->pValues;
}


signed char BaseWord::get_sign() const
{
	return this->sign;
}


signed short BaseWord::address_value() const
{
	// The minimum size of any word is 2
	assert(this->size >= 2);

	return this->value_according_to_field_specification(L0, R2);
}


signed int BaseWord::value_according_to_field_specification(const FieldSpecifications &fieldSpecification) const
{
	unsigned short left;
	unsigned short right;
	convert_field_specification_to_left_right(fieldSpecification, left, right);

	return this->value_according_to_field_specification(left, right);
}


signed int BaseWord::value_according_to_field_specification(const unsigned short fieldSpecification) const
{
	int right = fieldSpecification % 8;
	int left = (fieldSpecification - right) / 8;
	return this->value_according_to_field_specification(left, right);
}


signed int BaseWord::value_according_to_field_specification(const unsigned short left, const unsigned short right) const
{
	// left should be less than or equal to right
	assert(left <= right);

	// note: the range runs from 0 -> size and 0 is the SIGN
	// left cannot exceed the size of the word
	assert(left <= this->size);

	// right cannot exceed the size of the word
	assert(right <= this->size);

	bool includeSign = left == 0;

	// now we can make left and right the actual indices
	signed short leftIndex = (includeSign ? left : left - 1);
	signed short rightIndex = right - 1;

	signed int result = 0;
	while(leftIndex <= rightIndex)
	{
		result *= BaseWord::DISTINCT_BYTE_VALUES;
		result += this->pValues[leftIndex];
		leftIndex++;
	}

	if(includeSign)
	{
		result *= this->sign;
	}

	return result;
}


void BaseWord::value(const signed int value)
{
	// only change the sign when the value is not-zero
	if(value != 0)
	{
		this->set_sign(value > 0 ? +1 : -1);
	}

	// TODO, thomasde: use Helpers.h :: convert_to_mix_bytes

	unsigned int toSet = abs(value);

	unsigned int mod;
	for(int n = this->size - 1; n >= 0; n--)
	{
		mod = toSet % BaseWord::DISTINCT_BYTE_VALUES;
		this->pValues[n] = mod;

		toSet -= mod;

		assert(toSet % BaseWord::DISTINCT_BYTE_VALUES == 0);
		toSet /= BaseWord::DISTINCT_BYTE_VALUES;
	}

	// NOTE: we allow the operation to set whatever it needs to set
	//       and forget those pieces we couldn't
	if(toSet != 0)
	{
		throw overflow_error("The value is too large for the word.");
	}
}
